#!/usr/bin/env python

SVN_REPO_DEBUG = True
MANIFEST_MAX_DEPTH = 2
LOCAL_SCRIPT_NAME = "svn-repo.py"
PROGRESS_BAR_LENGTH_HALF = 30
PROGRESS_BAR_LENGTH = PROGRESS_BAR_LENGTH_HALF * 2

import sys, os, time, re, traceback
from sys import argv
from xml.dom.minidom import parse, Document

def pr_red_info(message):
	print "\033[31m%s\033[0m" % message

def pr_green_info(message):
	print "\033[32m%s\033[0m" % message

def pr_debug_info(message):
	if SVN_REPO_DEBUG:
		print message

def pr_bold_info(message):
	print "\033[1m%s\033[0m" % message

class CavanProgressBar:
	def __init__(self, total):
		self.init(total)

	def init(self, total):
		self.total = total
		self.current = 0
		self.update()

	def update(self):
		if self.total == 0:
			fill = PROGRESS_BAR_LENGTH
			percent = 100
		else:
			fill = self.current * PROGRESS_BAR_LENGTH / self.total
			percent = self.current * 100 / self.total

		left = right = ""
		for weight in range(0, PROGRESS_BAR_LENGTH_HALF):
			if weight < fill:
				left += "H"
			else:
				left += "="

		for weight in range(PROGRESS_BAR_LENGTH_HALF, PROGRESS_BAR_LENGTH):
			if weight < fill:
				right += "H"
			else:
				right += "="

		text = "[%s %d%% %s] [%d/%d]" % (left, percent, right, self.current, self.total)
		if SVN_REPO_DEBUG:
			print text
		else:
			sys.stdout.write(text + "\r")
			sys.stdout.flush()

	def add(self, count = 1):
		self.current += count
		self.update()

	def finish(self):
		if self.current < self.total:
			self.current = self.total
			self.update()

		if not SVN_REPO_DEBUG:
			print

class SvnRreoManager:
	def __init__(self):
		reload(sys)
		sys.setdefaultencoding("utf-8")

		self.project_root = os.path.abspath(".")
		self.config_root = os.path.join(self.project_root, ".svn_repo")
		self.svn_repo_mkdir(self.config_root)

		date = time.localtime(time.time())
		self.datename = "%04d%02d%02d%02d%02d%02d" % (date[0], date[1], date[2], date[3], date[4], date[5])

		self.logpath = os.path.join(self.config_root, "log")
		self.logfile = os.path.join(self.logpath, "%s.log" % self.datename)
		self.svn_repo_mkdir(self.logpath)

		self.diffpath = os.path.join(self.config_root, "diff/" + self.datename)
		self.svn_repo_mkdir(self.diffpath)

		self.manifest_xml = os.path.join(self.config_root, "manifest.xml")
		if os.path.isfile(self.manifest_xml):
			self.svn_repo_load_manifest(self.manifest_xml)
		else:
			self.url = None
			self.file_list = None
			self.project_list = None
		self.pattern = re.compile('.*/+\.[^/]+/*$')

	def svn_repo_mkdir(self, pathname):
		if os.path.isdir(pathname):
			return 0
		if self.svn_repo_mkdir(os.path.dirname(pathname)) < 0:
			return -1
		os.mkdir(pathname)
		return 0

	def svn_repo_load_manifest(self, manifest_xml):
		dom = parse(manifest_xml)
		if not dom:
			pr_red_info("Parse xml file `%s' failed" % manifest_xml)
			return -1

		tag_manifest = dom.documentElement
		tag_remote = tag_manifest.getElementsByTagName("remote").item(0)
		self.url = tag_remote.getAttribute("url")
		self.fetch = tag_remote.getAttribute("fetch")

		file_list = []
		for fn in tag_manifest.getElementsByTagName("file"):
			file_list.append(fn.getAttribute("path"))
		self.file_list = file_list

		project_list = []
		for fn in tag_manifest.getElementsByTagName("project"):
			project_list.append(fn.getAttribute("path"))
		self.project_list = project_list

		return 0

	def svn_repo_get_url(self):
		lines = self.svn_repo_popen("svn info | grep ^URL:.*")
		if lines == None or len(lines) < 1:
			return None
		return lines[0][4:]

	def svn_repo_system(self, command):
		pr_debug_info(command)
		if not SVN_REPO_DEBUG:
			command = command + ">> " + self.logfile
		while os.system(command) != 0:
			time.sleep(1)

		return 0

	def svn_repo_popen(self, command):
		pr_debug_info("[popen] " + command)
		fp = os.popen(command)
		if fp == None:
			return []
		lines = [line.strip() for line in fp.readlines()]
		fp.close()
		return lines

	def svn_repo_has_file(self, flist):
		for fn in flist:
			if fn.endswith("/"):
				continue
			return True
		return False

	def svn_repo_gen_xml_base(self, pathname, depth):
		if self.pattern.match(pathname):
			pr_red_info("Skipping project `%s'" % pathname)
			return 0
		flist = self.svn_repo_popen("svn list " + os.path.join(self.url, pathname))
		if depth < 1 or self.svn_repo_has_file(flist):
			pr_bold_info("Add directory " + pathname)
			tag_project = self.xml_dom.createElement("project")
			self.tag_manifest.appendChild(tag_project)
			tag_project.setAttribute("path", pathname[0:-1])
			tag_project.setAttribute("name", pathname[0:-1])
			return 0
		for dn in flist:
			self.svn_repo_gen_xml_base(os.path.join(pathname, dn), depth - 1)
		return 0

	def svn_repo_gen_xml(self):
		xml_dom = Document()
		tag_manifest = xml_dom.createElement("manifest")
		xml_dom.appendChild(tag_manifest)
		tag_remote = xml_dom.createElement("remote")
		tag_manifest.appendChild(tag_remote)
		tag_remote.setAttribute("url", self.url)
		tag_remote.setAttribute("fetch", "svn-master")

		self.xml_dom = xml_dom
		self.tag_manifest = tag_manifest

		for fn in self.svn_repo_popen("svn list " + self.url):
			if fn.endswith("/"):
				self.svn_repo_gen_xml_base(fn, MANIFEST_MAX_DEPTH)
			else:
				pr_bold_info("Add file " + fn)
				tag_file = xml_dom.createElement("file")
				tag_manifest.appendChild(tag_file)
				tag_file.setAttribute("path", fn)
				tag_file.setAttribute("name", fn)

		fd = open(self.manifest_xml, "w")
		try:
			fd.write(xml_dom.toprettyxml('\t', '\n', 'utf-8'))
		except:
			os.remove(self.manifest_xml)
			print traceback.format_exc()
			return -1

		return 0

	def svn_repo_init(self, argv):
		if len(argv) > 0:
			self.url = argv[0]
		elif os.path.isdir(".svn"):
			self.url = self.svn_repo_get_url()
		else:
			pr_red_info("Please give the url of svn")
			return -1

		if os.path.exists(".svn") and not os.path.isdir(".svn"):
			os.remove(".svn")

		if self.svn_repo_system("svn checkout --depth=empty %s ." % self.url) != 0:
			return -1

		return self.svn_repo_gen_xml()

	def svn_repo_remove_empty_file(self, pathname):
		st = os.stat(pathname)
		if st.st_size == 0:
			pr_debug_info("Remove empty file `%s'" % pathname)
			os.remove(pathname)
		return 0

	def svn_repo_remove_empty_files(self, dirname, suffix):
		for fn in os.listdir(dirname):
			if fn.endswith(suffix):
				self.svn_repo_remove_empty_file(os.path.join(dirname, fn))
		return 0

	def svn_repo_update(self):
		if self.file_list == None or self.project_list == None:
			pr_red_info("Please `run %s init' first" % sys.argv[0])
			return -1

		for fn in self.file_list:
			if self.svn_repo_system("svn update --force " + fn) < 0:
				return -1

		if not os.path.isdir(self.diffpath):
			self.svn_repo_mkdir(self.diffpath)

		bar = CavanProgressBar(len(self.project_list))
		for fn in self.project_list:
			project_path = os.path.join(self.project_root, fn)
			if os.path.isdir(project_path):
				os.chdir(project_path)
				diffpath = "%s/%s.diff" % (self.diffpath, fn.replace("/", "_"))
				self.svn_repo_system("git diff > %s" % diffpath)
				if self.svn_repo_system("git reset --hard") < 0:
					need_create = True
				else:
					self.svn_repo_remove_empty_file(diffpath)
					if self.svn_repo_system("rm .git/svn/.metadata.lock -rfv && git svn rebase") < 0:
						return -1
					need_create = False
			else:
				need_create = True

			if need_create:
				if os.path.exists(project_path):
					self.svn_repo_system("rm %s -rfv" % project_path)
				url = os.path.join(self.url, fn)
				if self.svn_repo_system("git svn init %s %s" % (url, project_path)) != 0:
					self.svn_repo_system("rm %s -rf" % project_path)
					return -1
				os.chdir(project_path)
				if self.svn_repo_system("git config pack.windowMemory 20m && git svn fetch && git svn rebase") != 0:
					self.svn_repo_system("rm %s -rf" % fn)
					return -1
				self.svn_repo_system("git checkout -b %s remotes/git-svn" % self.fetch)
			bar.add()
		bar.finish()

		self.svn_repo_remove_empty_files(self.diffpath, ".diff")
		return 0

	def svn_repo_command(self, argv):
		if self.project_list == None:
			pr_red_info("Please init first")
			return -1

		if len(argv) < 1:
			pr_red_info("Please give a command")
			return -1

		command = " ".join(argv)
		bar = CavanProgressBar(len(self.project_list))
		for fn in self.project_list:
			os.chdir(os.path.join(self.project_root, fn))
			command_last = command.replace("<path>", fn)
			if self.svn_repo_system(command_last) != 0:
				return -1
			bar.add()
		bar.finish()
		return 0

if __name__ == "__main__":
	if len(argv) < 2:
		pr_red_info("Please give a subcmd")
		sys.exit(-1)

	if os.path.exists(LOCAL_SCRIPT_NAME):
		os.remove(LOCAL_SCRIPT_NAME)
	os.symlink(os.path.abspath(argv[0]), LOCAL_SCRIPT_NAME)

	repo = SvnRreoManager()
	subcmd = argv[1]
	argv = argv[2:]

	if subcmd in ["clone", "init"]:
		ret = repo.svn_repo_init(argv)
	elif subcmd in ["update", "sync"]:
		ret = repo.svn_repo_update()
	elif subcmd in ["command", "cmd"]:
		ret = repo.svn_repo_command(argv)
	else:
		pr_red_info("unknown subcmd `%s'" % subcmd)
		ret = -1

	if ret < 0:
		pr_red_info("Failed")
	else:
		pr_green_info("OK")
